# 优化总结
什么是前端性能优化?
通常来说我们说的前端性能优化是指:从用户开始访问我们的网站到整个页面完整地展现出来的过程中,通过各种优化策略和优化方法,让页面加载的更快,让用户的操作响应更及时,给用户更好的使用体验。
前端性能优化是我们每个前端打工人都必须面对的问题,那怎么进行性能优化呢?下面我就给大家分享我所了解的一些性能优化知识,有什么疑问请大家提出来,谢谢~
# 性能优化指标与测量工具
工欲善其事必先利其器,在进行性能优化之前,我们应该准备好自己的性能优化测量工具,并且要有优化目标,即某项或者多项指标需要优化到什么程度。
# 常用的性能优化指标
- Speed Index(lighthouse,速度指数)
- TTFB(Network,第一个请求响应时间)
- 页面加载时间
- 首次渲染
- 交互动作的反馈时间
- 帧率FPS(动画 ctrl+shift+p)
- 异步请求完成时间
# 性能测量工具
Chrome DevTools
- 开发调试、性能评测
- Audit(Lighthouse)
- Throttling 调整网络吞吐
- Performance 性能分析
- Network 网络加载分析
Lighthouse
- 网站整体质量评估
- 还可以提出优化建议
WebPageTest
- 测试多地点(球各地的用户访问你的网站的性能情况)
- 全面性能报告(first view,repeat view,waterfall chart 等等)
- WebPageTest 还可以进行本地安装,让你的应用在还没上线的时候就可以测试。
- 更多请看 官网 (opens new window)
# 常用的性能测量API
计算一些关键的性能指标
window.addEventListener('load', (event) => {
// Time to Interactive
let timing = performance.getEntriesByType('navigation')[0];
// let timing = performance.timing
console.log(timing.domInteractive);
console.log(timing.fetchStart);
let diff = timing.domInteractive - timing.fetchStart;
console.log("TTI: " + diff);
})
2
3
4
5
6
7
8
9
以上代码只测量了"首次可交互时间",其它常用的性能指标还有:
DNS 解析耗时: domainLookupEnd - domainLookupStart
TCP 连接耗时: connectEnd - connectStart
SSL 安全连接耗时: connectEnd - secureConnectionStart
网络请求耗时 (TTFB): responseStart - requestStart
数据传输耗时: responseEnd - responseStart
DOM 解析耗时: domInteractive - responseEnd
资源加载耗时: loadEventStart - domContentLoadedEventEnd
First Byte时间: responseStart - domainLookupStart
白屏时间: responseEnd - fetchStart
首次可交互时间: domInteractive - fetchStart
DOM Ready 时间: domContentLoadEventEnd - fetchStart
页面完全加载时间: loadEventStart - fetchStart
http 头部大小: transferSize - encodedBodySize
重定向次数:performance.navigation.redirectCount
重定向耗时: redirectEnd - redirectStart
2
3
4
5
6
7
8
9
10
11
12
13
14
15
观察长任务(long tasks)
// 通过 PerformanceObserver 得到所有的 long task 对象
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(entry)
}
})
// 监听 long task
observer.observe({entryTypes: ['longtask']})
2
3
4
5
6
7
8
监听页面可见性的状态
let vEvent = 'visibilitychange';
if (document.webkitHidden != undefined) {
// webkit prefix detected
vEvent = 'webkitvisibilitychange';
}
function visibilityChanged() {
if (document.hidden || document.webkitHidden) {
console.log("Web page is hidden.")
} else {
console.log("Web page is visible.")
}
}
document.addEventListener(vEvent, visibilityChanged, false);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
通过监听页面可见性的状态可以判断用户是否在看当前页面,可以做一些处理,比如视频暂停,保存状态等等
判断用户网络状态
var connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
var type = connection.effectiveType;
function updateConnectionStatus() {
console.log("Connection type changed from " + type + " to " + connection.effectiveType);
type = connection.effectiveType;
}
connection.addEventListener('change', updateConnectionStatus);
2
3
4
5
6
7
8
9
通过判断用户网络状态,可以做一些针对性的加载,比如用户网络状态好,就可以加载一些更加高清的图片,反之加载一些质量更差的图片等等。
# 传输加载优化
- 减少 HTTP 请求
- 启用 Gzip 压缩
- 启用 Keep Alive 长连接(默认)
- HTTP 资源缓存
- Service workers
- 加速重复访问
- 离线支持
- 需要注意延长了首屏加载时间,但是页面总加载时间减少
- 启用 HTTP2
- 考虑用SSR技术
- 异步无阻塞加载JS(defer,async)
# 资源优化
- 资源压缩与合并
- 图片格式优化,选择合适的图片格式
- 图片加载优化(懒加载,渐进式加载,响应式图片,雪碧图)
- 字体优化
- 字体未下载完成时,浏览器隐藏或自动降级,导致字体闪烁
- font-display
- @font-face 字符集拆分
- ajax + base64
图片格式选择
JPEG
非常适合: 颜色丰富的照片,彩色图大焦点图、通栏 banner 图,结构不规则的图形
不适合:线条图形和文字、图标图形,因为它的压缩算法不太适合这些类型的图形,并且不支持透明度
压缩工具:jpegtran
PNG
非常适合: 纯色、透明、线条绘图、图标;边缘清晰、有大块相同颜色区域;颜色数较少单需要半透明
不适合:由于是无损存储,彩色图像体积太大,所以不太适合颜色丰富,彩色大图
压缩工具: node-pngquant-native
GIF
非常适合:动画,图标
不适合:每个像素只有 8 比特,不适合存储彩色图片
压缩工具:Gifsicle
Webp
非常适合:适用于图形和半透明图像
不适合: 最多处理 256 色,不适合彩色图片
真的需要图片吗?
Web font 代替图片
使用 Data URI 代替图片
采用 Image spriting(雪碧图)
# 渲染优化
- 避免回流与重绘
- 避免布局抖动(FastDom防止布局抖动利器)
- 将动画和经常变化的元素提到单独的图层
# 代码优化
# JS 优化
- Code splitting 代码拆分,按需加载
- Tree shaking 代码减重
- Scope Hoisting 模块合并
- 去除 console
- 避免长任务
- 避免超过 1kb 的行间脚本
- 大量事件绑定使用事件委托
- 适时使用防抖和节流
- 避免内存泄漏
- 使用 rAF 和 rIC 进行时间调度(react时间调度原理)
- 配合 V8 有效优化代码
- 源码 -> 抽象语法树 -> 字节码Bytecode -> 机器码
- 编译过程会进行优化(脚本流、字节码缓存、懒解析等)
- 运行时可能会发生反优化
- 对象优化可以做哪些
- 以相同顺序初始化对象成员,避免隐藏类的调整
- 实例化后避免添加新属性
- 尽量使用 Array 代替 array-like 对象
- 避免读取超过数组的长度
- 避免元素类型转换
# HTML 优化
- 精简 HTML 代码
- 减少 iframes 使用,延迟加载 iframe,父文档加载完毕后再给 iframe 的 src 赋值
- 压缩空白符
- 避免节点深层级嵌套
- 避免 table 布局
- 删除注释
- CSS&Javascript尽量外链
- 删除元素默认属性
# CSS 优化
提升 CSS 渲染性能
- 谨慎使用 expensive 昂贵的属性
- 如 :nth-child 伪类; position: fixed 定位
- 尽量减少样式层级数量
- 如 div ul li span i {color: true;}
- 尽量避免使用占用过多 CPU 和内存的属性
- 如 text-indent: -9999px
- 尽量避免使用耗电量大的属性
- 如 CSS3:3D transforms、CSS3 transitions、Opacity
- 尽量避免使用 CSS 表达式
- background-color: expression((new Date()).getHours() % 2 ? "#FFF" : "#000")
- 尽量避免使用通配选择器,有选择地使用选择器
- body > a {font-weight: blod;}
- 尽量避免类正则的属性选择器
- *=,|=,^=,$=
- 使用 contain 属性,告诉浏览器某些方面可以这样优化,哪些不能优化
- 使用 font-display 属性,帮助我们把文字更早显示在页面上,还可以减少文字闪动问题
提升 CSS 文件加载性能
- 内联首屏关键CSS(Critical CSS)
- 使用外链的 CSS
- 尽量避免使用 @import
- 异步加载CSS
精简 CSS 代码
- 使用后缩写语句
- 删除不必要的零
- 删除不必要的单位,如px
- 删除过多的分号
- 删除空格的注释
- 尽量减少样式表的大小
合理使用 Web Fonts
- 将字体部署在 CDN 上
- 将字体以 base64 形式保存在 CSS 中并通过 localStorage 进行缓存
- Google 字体库因为某些不可抗原因,应该使用国内托管服务
CSS 动画优化
- 尽量避免同时动画
- 延迟动画初始化
- 结合 SVG
# 构建优化
加快构建速度(打包速度)
使用 speed-measure-webpack-plugin 插件可以测量各个插件和loader所花费的时间,量化打包速度,判断优化效果
- 缩小文件的搜索范围(配置include/exclude resolve.modules resolve.mainFields alias noParse extensions)
- 通过 exclude、include 配置来确保转译尽可能少的文件
- 优化 resolve.modules 配置
- 优化 resolve.mainFields 配置
- alias
- noParse
- extensions
- 在一些性能开销较大的 loader 之前添加 cache-loader,将结果缓存中磁盘中
- 使用 happypack 开启多进程打包
- 除了使用 Happypack 外,我们也可以使用 thread-loader 开启多进程打包 loader
- 使用 HardSourceWebpackPlugin 为模块提供中间缓存,第二次构建可大量节约时间
- 使用 IgnorePlugin 忽略第三方包指定目录,例如 moment 的本地语言包
- 使用 webpack-parallel-uglify-plugin 开启 JS 多进程压缩
减少打包文件体积
引入 webpack-bundle-analyzer 分析打包后的文件,判断哪些包还可以拆分和优化
- 使用 externals 配置,然后将 JS 文件、CSS 文件和存储在 CDN
- 使用 DllPlugin(动态链接库)将 bundles 拆分,使用 DllReferencePlugin(索引链接) 对 manifest.json 引用,让一些基本不会改动的代码先打包成静态资源,避免反复编译浪费时间
- 使用 optimization.splitChunks 配置抽离公共代码
- 使用 IgnorePlugin 忽略第三方包指定目录,例如 moment 的本地语言包(重复)
- 使用 url-loader 或 image-webpack-loader 对图片进行转化或者压缩处理
- 优化 SourceMap,开发环境推荐: cheap-module-eval-source-map,生产环境推荐: cheap-module-source-map
- 按需加载,项目中的路由懒加载
- webpack自身的优化:
- tree-shaking,在生产环境下,会自动移除没有使用到的代码
- scope hosting 作用域提升,变量提升,可以减少一些变量声明
- babel 配置的优化,配置 @babel/plugin-transform-runtime,重复使用 Babel 注入的帮助程序,以节省代码大小的插件
# 更多流行优化技术
- 移动端使用 SVG 图标
- 使用 Flexbox 优化布局
- 优化资源加载顺序,给资源设置优先级
- Preload:提前加载较晚出现,但对当前页面非常重要的资源
- Prefetch:提前加载后继路由需要的资源,优先级低
- dns-prefetch:对链接进行 DNS 预解析
- prerender:标识下一个导航可能需要的资源
- 预渲染页面(react-snap,vue也可以使用)
- Windowing、虚拟列表等提高列表性能
- 使用骨架组件减少布局移动和页面抖动
不同的应用场景可能会采用不同的优化方案和优化技术,对于一个应用,不能所有的优化都一股脑想用上去,因为优化也会带来一定的成本,比如配置麻烦,需要遵守一些特定的规则等等。
# 数据库优化
- 查询语句无论是使用哪种判断条件:等于、小于、大于,WHERE 左侧的条件查询字段不要使用函数或者表达式
- 使用 EXPLAIN 命令优化你的 SELECT 查询,对于复杂、效率低的 SQL 语句,我们通常是使用 explain sql 来分析这条 SQL 语句,这样方便我们分析,进行优化
- 当你的 SELECT 查询语句只需要使用一条记录时,要使用 LIMIT 1
- 不要直接使用 SELECT *,而应该使用需要查询的表字段,因为使用 EXPLAIN 进行分析时,SELECT * 使用的是全表扫描,也就是 type = all
- 为没一张表设置一个 ID 属性
- 避免再 WHERE 字句中对字段进行 NULL 判断
- 避免再 WHERE 中使用 != 或
<>
操作符 - 使用 BETWEEN AND 替代 IN
- 为搜索字段创建索引
- 选择正确的存储引擎,innoDB、MyISAM、MEMORY 等
- 使用
LINK %abc%
不会走索引,而使用LINK abc%
会走索引 - 对于美剧类型的字段(即有固定罗列值得字段),建议使用 EMUM 而不是 VARCHAR,如性别、星期、类型、类别等
- 拆分打得 DELETE 或 INSERT 语句
- 选择合适得字段类型,选择标准是:尽可能小、尽可能定长、尽可能使用整数
- 字段设计尽可能使用 NOT NULL
- 进行水平切割或者垂直分割
水平分割:通过建立结构相同得几张表分别存储数据 垂直分割:将经常一起使用得字段放在一个单独的表中,分割后的表记录之间是一一对应的关系
# java 代码优化
- 尽量指定类、方法的 final 修饰符
- 尽量重用对象
- 尽可能使用局部变量
- 及时关闭流
- 尽量减少对变量的重复计算
- 尽量采用懒加载的策略,即在需要的时候才创建
- 慎用异常
- 不要在循环中使用
try...catch...
,应该把其放在最外层 - 如果能估计到待添加的内容长度,为底层以数组方式实现的集合、工具类指定初始长度
- 当复制大量数据时,使用
System.arraycopy()
命令 - 乘法和除法使用移位操作
- 循环内不要不断创建对象引用
- 基于效率和类型检查的考虑,应该尽可能使用array,无法确定数组大小时才使用 ArrayList
- 尽量使用 HashMap、ArrayList、StringBuilder,除非线程安全需要,否则不推荐使用 Hashtable、Vector、StringBuffer,后三者由于使用同步机制而导致了性能开销
- 不要将数组声明为 public static final
- 尽量在合适的场合使用单例
- 尽量避免随意使用静态变量
- 及时清除不再需要的会话
- 避免内存泄漏
- 字符串变量和字符串常量 equals 的时候将字符串常量写在前面,避免空指针异常